package org.judo.database.orm.mappers;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.judo.classlocator.ClassLocator;
import org.judo.database.AbstractDatabase;
import org.judo.database.JudoResultSet;
import org.judo.database.TypeTranslator;
import org.judo.database.exceptions.DatabaseConnectionException;
import org.judo.database.exceptions.DatabaseException;
import org.judo.database.exceptions.ORMException;
import org.judo.database.profile.ColumnSchema;
import org.judo.database.profile.EntityProfiler;
import org.judo.database.profile.EntitySchema;
import org.judo.database.profile.TableSchema;
import org.judo.propertyobject.PropertyNotFoundException;
import org.judo.propertyobject.PropertyObject;
import org.judo.util.JavaNameConversion;
import org.judo.util.Util;

/**
 * Look into [Entity.prop] type syntax for getting mapping
 * information about an entity from the Entity Profieler
 * 
 * TODO: Make sure all sql statements are parsed
 */
public class ReflectionMapper extends AbstractDatabase
{
	/*
	 * 
	 * ************************************************
	 * *****************DELETE METHODS*****************
	 * ************************************************
	 * 
	 */
	
	public void delete(PropertyObject obj,int id) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		obj.moveFromColToMap();	
	}

	public void delete(String entityName,int id) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		
	}
	
	public void delete(String entityName,PropertyObject obj) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		obj.moveFromColToMap();
		
		EntitySchema entity = EntityProfiler.getProfile(obj,
				(Connection)con.getConnectionService(),entityName);
		
		TableSchema table = entity.tableSchema;
		ArrayList<ParamaterizedValue> valueList = new ArrayList<ParamaterizedValue>();
		
		String sql = "delete from "+table.tableName+" "+addWhereClause(obj,table,valueList,false);	
		exec(sql,valueList);
	}
	
	public void delete(PropertyObject obj) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		obj.moveFromColToMap();
		
		EntitySchema entity = EntityProfiler.getProfile(obj,
				(Connection)con.getConnectionService(),null);
		
		TableSchema table = entity.tableSchema;
		ArrayList<ParamaterizedValue> valueList = new ArrayList<ParamaterizedValue>();
		
		String sql = "delete from "+table.tableName+" "+addWhereClause(obj,table,valueList,false);	
		exec(sql,valueList);
	}

	public void delete(PropertyObject obj, String deleteStatement) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		obj.moveFromColToMap();
		deleteStatement = obj.parse(deleteStatement);
		exec(deleteStatement);
	}

	/*
	 * 
	 * ************************************************
	 * *****************INSERT METHODS*****************
	 * ************************************************
	 * 
	 */	
	
	public void insert(String entityName, PropertyObject obj) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		obj.moveFromColToMap();
		createAndExecuteInsertStatement(entityName,obj);
		insertChildren(obj);
		obj.managed(true);
	}
	
	/**
	 * TODO: Cache statements so they dont need to be regenerated
	 * for each entity type each time.
	 */
	public void insert(PropertyObject obj) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		obj.moveFromColToMap();
		createAndExecuteInsertStatement(null,obj);
		insertChildren(obj);
		obj.managed(true);
	}

	/**
	 * If the entity has an autoincrement field, this method will populate
	 * the property on the PropertyObject with the value after the insert.
	 * This method can not support more than one auto increment value per table.
	 * 
	 * TODO: Check Success
	 * 
	 */
	public void insert(PropertyObject obj, String insertStatement) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{			
		PreparedStatement stmnt = null;
		
		try
		{
			try
			{
				obj.moveFromColToMap();
				
				Connection connection = (Connection)con.getConnectionService();
				
				String sql = obj.parse(insertStatement);
				sql = sql.trim();
				String parts[] = sql.split(" ");
				
				if (parts.length < 3)
					throw new DatabaseException("Could not insert '"+obj.getClass().getCanonicalName()+"' with '"+insertStatement+"' because the statement appears to be invalid.");
				
				String tableName = parts[2]; 
				TableSchema table = EntityProfiler.getTableSchema(tableName,obj,connection);
				
				if (table == null)
					throw new DatabaseException("Could not insert '"+obj.getClass().getCanonicalName()+"' with '"+insertStatement+"' because Judo could not determine what the table name is or the table does not exist in the database.  Judo's best guess at the table name is: "+tableName);
				
				stmnt = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
				stmnt.executeUpdate();
				setAutoValue(stmnt,table,obj);
				insertChildren(obj);
				obj.managed(true);
			}
			finally
			{
				if (stmnt != null)
					stmnt.close();
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new DatabaseException("Could not insert '"+obj.getClass().getCanonicalName()+"' with '"+insertStatement+"' because: "+e);
		}
	}

	/*
	 * 
	 * ************************************************
	 * *****************FIND METHODS*******************
	 * ************************************************
	 * 
	 */	
	
	public PropertyObject find(String sqlStatement) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		JudoResultSet rs = performQuery(sqlStatement,PropertyObject.class);
		
		if (rs.size() > 0)
			return (PropertyObject)rs.get(0);
		
		return null;
	}
	
	public PropertyObject find(Class resultType, String whereClause) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		whereClause = whereClause.trim();
		
		JudoResultSet rs = null;
		
		if (whereClause.startsWith("select") || whereClause.startsWith("SELECT"))
			rs = performQuery(whereClause,resultType);
		else
			rs = performQuery("select * from "+getTableName(resultType)+" where "+whereClause,resultType);
		
		if (rs.size() > 0)
			return (PropertyObject)rs.get(0);
		
		return null;
	}

	public PropertyObject find(Class resultType, int id) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		try
		{
			EntitySchema entity =EntityProfiler.getProfile((PropertyObject)resultType.newInstance(),
					(Connection)con.getConnectionService(),null);
			
			TableSchema table = entity.tableSchema;
			int pkCount = table.primaryKeys.size();
			
			if (pkCount != 1)
				throw new ORMException("Could not get information about enity '"+resultType.getCanonicalName()+"' because when the 'find(Class resultType, int id)' is used, it can only target entities that have one int type PK column and this one has "+pkCount+" PKs");
			
			String pkColumn = table.primaryKeys.get(0);
			String sql = "select * from "+table.tableName+" where "+pkColumn+" = "+id;
			JudoResultSet rs = performQuery(sql,resultType);
			
			if (rs.size() > 0)
				return (PropertyObject)rs.get(0);
			
			return null;
		} 
		catch (InstantiationException e){
			throw new ORMException("Could not get information about enity '"+resultType.getCanonicalName()+"' because: "+e);
		} 
		catch (IllegalAccessException e){
			throw new ORMException("Could not get information about enity '"+resultType.getCanonicalName()+"' because: "+e);
		}
	}
	
	/*
	 * 
	 * ************************************************
	 * *****************QUERY METHODS******************
	 * ************************************************
	 * 
	 */
	
	public JudoResultSet query(String sqlStatement) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		return performQuery(sqlStatement,PropertyObject.class);
	}

	public JudoResultSet query(Class resultType, String whereClause) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		whereClause = whereClause.trim();
		
		if (whereClause.startsWith("select") || whereClause.startsWith("SELECT"))
		{
			return performQuery(whereClause,resultType);
		}
		else
		{
			String sqlStatement = "select * from "+getTableName(resultType)+" where "+whereClause;
			return performQuery(sqlStatement,resultType);
			
		}
	}

	public JudoResultSet queryEntity(String entityName) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		String sqlStatement = "select * from "+entityName;
		return performQuery(sqlStatement,PropertyObject.class);
	}

	public JudoResultSet queryEntity(String entityName,String whereClause) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		String sqlStatement = "select * from "+entityName+" where "+whereClause;
		return performQuery(sqlStatement,PropertyObject.class);
	}
	
	public JudoResultSet queryEntity(String entityName, String... orderByColumns) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		String sqlStatement = "select * from "+entityName+" order by ";
		boolean first = true;
		
		for (String part: orderByColumns)
		{
			if (first)
				first = false;
			else
				sqlStatement += ",";
			
			sqlStatement += part;
		}
		
		return performQuery(sqlStatement,PropertyObject.class);
	}	
	
	public JudoResultSet queryAll(Class resultType) 
		throws DatabaseConnectionException,ORMException,DatabaseException
	{
		String sqlStatement = "select * from "+getTableName(resultType);
		return performQuery(sqlStatement,resultType);
	}

	public JudoResultSet queryAll(Class resultType, String... orderByColumns) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		String sqlStatement = "select * from "+getTableName(resultType)+" order by ";
		boolean first = true;
		
		for (String part: orderByColumns)
		{
			if (first)
				first = false;
			else
				sqlStatement += ",";
			
			sqlStatement += part;
		}
		
		return performQuery(sqlStatement,resultType);
	} 
	
	/*
	 * 
	 * ************************************************
	 * *****************UPDATE METHODS*****************
	 * ************************************************
	 * 
	 */	
	
	public void update(String entityName,PropertyObject obj) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		updateObj(obj,entityName);
	}
	
	/**
	 * If the table has an attribute with the java name of 'rowver'
	 * then this method performs an optimistic concurency check
	 */
	public void update(PropertyObject obj) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		updateObj(obj,null);
	}

	/**
	 * Unlike update(PropertyObject obj), this method does not perform
	 * an optimistic concurency check since the user specifies the
	 * SQL.  It must be done manually 
	 */
	public void update(PropertyObject obj, String sql) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		sql = obj.parse(sql);
		exec(sql);
	}
	
	/*
	 * 
	 * ************************************************
	 * *******************EXEC METHODS*****************
	 * ************************************************
	 * 
	 */
	
	public int exec(String sql,ArrayList values) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		int result = 0;
		
		Connection connection = (Connection)con.getConnectionService();
		PreparedStatement stmnt = null;
		
		try
		{
			try
			{
				sql = (new PropertyObject()).parse(sql);
				System.out.println("SQL"+sql);
				
				stmnt = connection.prepareStatement(sql);
				setStmntValues(values,stmnt);
				result = stmnt.executeUpdate();
			}
			finally
			{
				if (stmnt != null)
					stmnt.close();
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new DatabaseException("Could not execute SQL: '"+sql+"' because: "+e);
		}
		
		return result;
	}
	
	public int exec(String sql) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		int result = 0;
		
		Connection connection = (Connection)con.getConnectionService();
		Statement stmnt = null;
		
		try
		{
			try
			{
				sql = (new PropertyObject()).parse(sql);
				stmnt = connection.createStatement();
				result = stmnt.executeUpdate(sql);
			}
			finally
			{
				if (stmnt != null)
					stmnt.close();
			}
		}
		catch(Exception e)
		{
			throw new DatabaseException("Could not execute SQL: '"+sql+"' because: "+e);
		}
		
		return result;
	}

	/*
	 * 
	 * ************************************************
	 * ********************OTHER METHODS***************
	 * ************************************************
	 * 
	 */	
	
	public void manuallyCommit() 
		throws DatabaseException
	{
		con.commitTransaction();
	}
	
	public void manuallyRollback() 
		throws DatabaseException
	{	
		con.rollbackTransaction();
	}

	
	/*
	 * 
	 * ************************************************
	 * *****************INTERNAL METHODS***************
	 * ************************************************
	 * 
	 */
	
	protected void setStmntValues(ArrayList<ParamaterizedValue> values, PreparedStatement stmnt) 
		throws PropertyNotFoundException, SQLException, ORMException
	{
		int col = 1;
		
		for (ParamaterizedValue value: values)
		{
			if (value.value == null)
				stmnt.setNull(col,TypeTranslator.getSQLType(value.type));
			else
				stmnt.setObject(col,value.value);
			
			col++;
		}
	}
		
	/**
	 * TODO: Look into [Entity.prop] type syntax
	 */
	protected JudoResultSet performQuery(String sqlStatement,Class type) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		JudoResultSet ret = new JudoResultSet();
		PreparedStatement stmnt = null;
		ResultSet rs = null;
		
		ArrayList autoIncrementNames = new ArrayList();
		
		try
		{
			try
			{
				sqlStatement = (new PropertyObject()).parse(sqlStatement);
				
				System.out.println("SQL: "+sqlStatement);
				Connection connection = (Connection)con.getConnectionService();
				stmnt = connection.prepareStatement(sqlStatement);
				rs = stmnt.executeQuery();
				
				ResultSetMetaData metaData = rs.getMetaData();
				int count = metaData.getColumnCount();
				
				for (int i=0;i<count;i++)
				{
					int cur = i+1;
					String name = metaData.getColumnName(cur);
					String javaName = JavaNameConversion.getMemberName(name);
					ret.addPropName(javaName);
					
					if (metaData.isAutoIncrement(cur))
						autoIncrementNames.add(javaName);
				}
				
				while (rs.next())
				{
					PropertyObject obj = (PropertyObject)type.newInstance();
					HashMap originalValues = new HashMap();
					obj.managed(true);
					
					for (int i=0;i<count;i++)
					{
						int cur = i+1;
						String name = metaData.getColumnName(cur);
						Object value = rs.getObject(cur);
						
						String javaName = JavaNameConversion.getMemberName(name);
						obj.set(javaName,value);
						
						if (autoIncrementNames.contains(javaName))
							obj.isAutoIncrement(true);
						
						obj.moveFromMapToCol();
						originalValues.put(javaName,value);
					}
					
					obj.allOriginalValues(originalValues);
					ret.add(obj);
					
					setupLazyInit(obj,type);
				}
			}
			finally
			{
				if (rs != null)
					rs.close();
				
				if (stmnt != null)
					stmnt.close();
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new DatabaseException("Could not perform query '"+sqlStatement+"' because: "+e);
		}
		
		return ret;
	}
	
	protected void createAndExecuteInsertStatement(String entityName, PropertyObject obj)
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		EntitySchema entity = EntityProfiler.getProfile(obj,
				(Connection)con.getConnectionService(),entityName);
		
		TableSchema table = entity.tableSchema;
	
		if (entityName == null)
			entityName = table.tableName;
			
		String sql = "insert into "+entityName+" ";
		
		String columns = "(";
		String values = " values (";
		ArrayList<ParamaterizedValue> valueList = new ArrayList<ParamaterizedValue>();
		
		boolean first = true;
		
		for (ColumnSchema column: table.columnList)
		{
			String javaPropName = JavaNameConversion.getMemberName(column.name);
			
			if (column.isAutoIncrement)
			{
				try{
					String value = ""+obj.get(javaPropName);
						
					if (value.equals("") || value.equals("0") || value.equals("0.0") || value.equals("null"))
						continue;
					else
						throw new ORMException("The property '"+javaPropName+"' from '"+obj.getClass().getCanonicalName()+"' is mapped to column '"+table.tableName+"."+column.name+"' which is defined as an auto-increment field but the property has value: "+value+". Auto increment fields should not be populated with values before inserts.");
				} 
				catch (PropertyNotFoundException e){
					throw new ORMException("Error getting property '"+javaPropName+"' from '"+obj.getClass().getCanonicalName()+"': "+e);
				}
			}
			
			if (first)
				first = false;
			else
			{
				columns += ",";
				values += ",";
			}
			
			columns += column.name;
			values += "?";
			
			try
			{
				ParamaterizedValue pValue = new ParamaterizedValue();
				
				pValue.value = obj.get(javaPropName);
				pValue.type = column.type;
				
				if (!column.isNullable && pValue.value == null)
					throw new ORMException(
							"The property '"+javaPropName+"' in class '"+obj.getClass().getCanonicalName()+"' can not have a null value");
				
				valueList.add(pValue);
			} 
			catch (PropertyNotFoundException e){
				throw new ORMException("Error getting property '"+javaPropName+"' from "+obj.getClass().getCanonicalName()+": "+e);
			}
		}
		
		columns += ") ";
		values += ") ";
		
		sql += columns + values;
		
		Connection connection = (Connection)con.getConnectionService();
		PreparedStatement stmnt = null;
		
		try
		{
			try
			{
				obj.moveFromColToMap();
				
				sql = (new PropertyObject()).parse(sql);
				System.out.println("SQL"+sql);
				
				stmnt = connection.prepareStatement(sql);
				setStmntValues(valueList,stmnt);
				int result = stmnt.executeUpdate();
				setAutoValue(stmnt,table,obj);
			}
			finally
			{
				if (stmnt != null)
					stmnt.close();
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
			throw new DatabaseException("Could not execute SQL: '"+sql+"' because: "+e);
		}
	}

	protected void setAutoValue(PreparedStatement stmnt,TableSchema table,PropertyObject obj) 
		throws ORMException, SQLException
	{
		ResultSet rs = stmnt.getGeneratedKeys();
		
		if (rs == null)
			return;
		
		if (rs.next())
		{
			int id = (int)Double.parseDouble(rs.getString(1));
			
			for (ColumnSchema col: table.columnList)
			{
				String javaName = JavaNameConversion.getMemberName(col.name);
				
				if (col.isAutoIncrement && obj.propertyExists(javaName))
				{	
						obj.set(javaName,id);
						break;
				}
			}
		}
	}
	
	protected String addWhereClause(PropertyObject obj,TableSchema table,ArrayList<ParamaterizedValue> valueList,boolean useOriginalValues) 
		throws ORMException
	{
		String sql = " where ";
		
		boolean first = true;
		
		if (table.primaryKeys.size() != 0)
		{
			for (String key: table.primaryKeys)
			{
				if (first)
					first = false;
				else
					sql += " and ";
				
				sql += key+" = ? ";
				
				ColumnSchema col = table.columns.get(key);
				ParamaterizedValue value = new ParamaterizedValue();
				String javaName = JavaNameConversion.getMemberName(col.name);
				
				try{
					value.value = obj.get(javaName);
				}
				catch (PropertyNotFoundException e){
					throw new ORMException("The table '"+table.tableName+"' has a primary key defined as '"+key+"' but it is not in the mapped class '"+obj.getClass().getCanonicalName()+"'.");
				}
				
				Util.out(javaName+"="+value.value);
				
				value.type = col.type;
				valueList.add(value);
			}
		}
		else
		{
			for (ColumnSchema column: table.columnList)
			{
				if (first)
					first = false;
				else
					sql += " and ";
				
				sql += column.name+" = ? ";
				
				ParamaterizedValue value = new ParamaterizedValue();
				String javaName = JavaNameConversion.getMemberName(column.name);
				
				if (useOriginalValues)
					value.value = obj.allOriginalValues().get(javaName);
				else
				{
					try{
						value.value = obj.get(javaName);
					} 
					catch (PropertyNotFoundException e){
						throw new ORMException("Could not build where clause for entity '"+obj.getClass().getCanonicalName()+"' because property '"+javaName+"' was not found"); 
					}
				}
					
				
				value.type = column.type;
				valueList.add(value);
			}
		}
		
		return sql;
	}

	protected String getTableName(Class poClass)
		throws ORMException
	{
		try
		{
			PropertyObject propertyObject = (PropertyObject)poClass.newInstance();
			return propertyObject.persistedName();
		} 
		catch (Exception e){
			throw new ORMException("Could not determine table name because: "+e);
		}
	}
	
	public void updateObj(PropertyObject obj,String name) 
		throws DatabaseConnectionException, ORMException, DatabaseException
	{
		EntitySchema entity = EntityProfiler.getProfile(obj,
				(Connection)con.getConnectionService(),name);
		
		TableSchema table = entity.tableSchema;
		String sql = "update "+table.tableName+" set ";
		
		ArrayList<ParamaterizedValue> valueList = new ArrayList<ParamaterizedValue>();
		
		boolean thereWasAChange = false;
		boolean first = true;
		
		for (ColumnSchema column: table.columnList)
		{
			if (column.isAutoIncrement)
				continue;
			
			String javaName = JavaNameConversion.getMemberName(column.name);
			
			if (obj.propertyExists(javaName))
			{	
				try{
					if (obj.hasChangedSinceQuery(javaName))
					{
						thereWasAChange = true;
						
						if (first)
							first = false;
						else
							sql += ",";
						
						sql += column.name+" = ? ";
						
						ParamaterizedValue value = new ParamaterizedValue();
						Util.out(javaName+"="+obj.get(javaName));
						value.value = obj.get(javaName);
						value.type = column.type;
						valueList.add(value);
					}
				} 
				catch (PropertyNotFoundException e){
					throw new ORMException("Internal Judo Error while building insert statement.  The property '"+javaName+"' was expected to exist in: "+obj.getClass().getCanonicalName());
				}
			}
		}
		
		updateChildren(obj,obj.getClass());
		
		if (!thereWasAChange)
			return;
		
		sql += addWhereClause(obj,table,valueList,true);
		exec(sql,valueList);
	}
	
	/**
	 * Parent must have a single PK and child must have a property with the
	 * same name as the parent's PK.
	 */
	protected void setupLazyInit(PropertyObject obj,Class type)
		throws ORMException, DatabaseConnectionException, DatabaseException
	{
		List<String> propNames = obj.propNames();
		EntitySchema parentEntity = null;
		
		try
		{
			parentEntity = EntityProfiler.getProfile((PropertyObject)obj,
				(Connection)con.getConnectionService(),null);
		}
		catch(Exception e)
		{
			return;
		}
		
		if (parentEntity.tableSchema.primaryKeys.size() != 1)
			return;
		
		String parentName = parentEntity.tableSchema.tableName;
		String pkName = parentEntity.tableSchema.primaryKeys.get(0); 
		
		for (String name:propNames)
		{
			Object propVal = obj.get(name);
			
			if (propVal != null && propVal instanceof java.util.List && name.endsWith("Children"))
			{
				String childName = name.replace("Children","");
				String sqlName = JavaNameConversion.getUnderscoreTableName(childName);
				
				char chars[] = childName.toCharArray();
				
				if (Character.isLowerCase(chars[0]))
					chars[0] = Character.toUpperCase(chars[0]);
				
				String className = new String(chars);
				String packageName = type.getPackage().getName();
				
				try
				{
					Class childClass = ClassLocator.findClassInProject(className,packageName);
					
					EntitySchema childEntity = EntityProfiler.getProfile((PropertyObject)childClass.newInstance(),
							(Connection)con.getConnectionService(),null);
					
					String childTableName = childEntity.tableSchema.tableName;
					
					String sql  = "select * from "+childTableName+","+parentName+" where "+parentName+"."+pkName+" = "+childTableName+"."+pkName;
					
					LazyInitList newList = new LazyInitList();
					newList.defineChildClass(childClass);
					newList.definePopulateSQL(sql);
					newList.defineReflectionMapper(this);
					
					/*
					 *Just incase there are any objects in the original list
					 *put them in the new list
					 */
					newList.addAll((Collection)propVal);
					
					//TODO: Should this be a direct field set?
					obj.set(name,newList);
				}
				catch(ClassNotFoundException e)
				{
					throw new ORMException("Problem mapping class.  The object '"+type.getCanonicalName()+"' has the field '"+name+"' that follows the naming convention used to specify a one to many relationship with another entity.  Using this convention a class by the name '"+className+"' was attempted to be loaded but could not be found");
				}
				catch (InstantiationException e)
				{
					throw new ORMException("Problem mapping class.  The object '"+type.getCanonicalName()+"' has the field '"+name+"' that follows the naming convention used to specify a one to many relationship with another entity.  When attempting to create an instance of this class, the following problem was encountered: "+e);
				} 
				catch (IllegalAccessException e)
				{
					throw new ORMException("Problem mapping class.  The object '"+type.getCanonicalName()+"' has the field '"+name+"' that follows the naming convention used to specify a one to many relationship with another entity.  When attempting to create an instance of this class, the following problem was encountered: "+e);
				}
			}
		}
	}
	
	/*
	 * Currently this works only if the parent has one PK
	 * and the child has a FK of that same name
	 */
	private void insertChildren(PropertyObject obj)
		throws ORMException, DatabaseConnectionException, DatabaseException
	{
		EntitySchema parentEntity = null;
		
		try
		{
			parentEntity = EntityProfiler.getProfile((PropertyObject)obj,
				(Connection)con.getConnectionService(),null);
		}
		catch(Exception e)
		{
			return;
		}
		
		String pkName = parentEntity.tableSchema.primaryKeys.get(0);
		pkName = JavaNameConversion.getMemberName(pkName);
		Object pkValue = obj.get(pkName);
		List<String> propNames = obj.propNames();
		
		for (String name:propNames)
		{
			Object propVal = obj.get(name);
			
			if (propVal != null && propVal instanceof java.util.List && name.endsWith("Children") && propVal instanceof List)
			{
				String childName = name.replace("Children","");
				List list = (List)propVal;
				
				for (int i=0;i<list.size();i++)
				{
					PropertyObject child = (PropertyObject)list.get(i);
					
					if (!child.hasField(pkName))
						throw new ORMException("Error attempting to save children on class '"+obj.getClass().getName()+"'. The field '"+propVal.getClass().getName()+"' follows the naming convention (ends with 'Children') that specifies it contains child entities, but it holds a class '"+child.getClass().getName()+"' that does not have a Foreign Key field named '"+pkName+"'");
						
					child.set(pkName,pkValue);
					insert(child);
				}
			}
		}
	}
	
	/**
	 * Parent must have a single PK and child must have a property with the
	 * same name as the parent's PK.
	 */
	protected void updateChildren(PropertyObject obj,Class type)
		throws ORMException, DatabaseConnectionException, DatabaseException
	{
		List<String> propNames = obj.propNames();
		EntitySchema parentEntity = null;
		
		try
		{
			parentEntity = EntityProfiler.getProfile((PropertyObject)obj,
				(Connection)con.getConnectionService(),null);
		}
		catch(Exception e)
		{
			return;
		}
		
		if (parentEntity.tableSchema.primaryKeys.size() != 1)
			return;
		
		String parentName = parentEntity.tableSchema.tableName;
		String pkName = parentEntity.tableSchema.primaryKeys.get(0); 
		
		for (String name:propNames)
		{
			Object propVal = obj.get(name);
			
			if (propVal != null && propVal instanceof Collection && name.endsWith("Children"))
			{
				String childName = name.replace("Children","");
				String sqlName = JavaNameConversion.getUnderscoreTableName(childName);
				
				char chars[] = childName.toCharArray();
				
				if (Character.isLowerCase(chars[0]))
					chars[0] = Character.toUpperCase(chars[0]);
				
				String className = new String(chars);
				String packageName = type.getPackage().getName();
				
				try
				{
					Class childClass = ClassLocator.findClassInProject(className,packageName);
					
					EntitySchema childEntity = EntityProfiler.getProfile((PropertyObject)childClass.newInstance(),
							(Connection)con.getConnectionService(),null);
					
					String childTableName = childEntity.tableSchema.tableName;
					
					debug("----");
					debug("Prop: "+name);
					debug("Child Class: "+childClass);
					debug("Type: "+propVal.getClass().getCanonicalName());
					debug("Child Table Name: "+childTableName);
					
					Collection children = (Collection)propVal;
					Iterator childrenList = children.iterator();
					
					while (childrenList.hasNext())
					{
						PropertyObject childObj = (PropertyObject)childrenList.next();
						
						if (childObj.managed())
						{
							debug("--Is Managed");
							/*
							 * If it is managed that means it existed in the
							 * database at the time of query
							 */
							update(childObj);
						}
						else
						{
							debug("--Not Managed");
							/*
							 * It it is not managed so it is a new object that
							 * isnt in the databases
							 */
							//if (childObj.)
							insert(childObj);
						}
					}
				}
				catch(ClassNotFoundException e)
				{
					throw new ORMException("Problem mapping class.  The object '"+type.getCanonicalName()+"' has the field '"+name+"' that follows the naming convention used to specify a one to many relationship with another entity.  Using this convention a class by the name '"+className+"' was attempted to be loaded but could not be found");
				}
				catch (InstantiationException e)
				{
					throw new ORMException("Problem mapping class.  The object '"+type.getCanonicalName()+"' has the field '"+name+"' that follows the naming convention used to specify a one to many relationship with another entity.  When attempting to create an instance of this class, the following problem was encountered: "+e);
				} 
				catch (IllegalAccessException e)
				{
					throw new ORMException("Problem mapping class.  The object '"+type.getCanonicalName()+"' has the field '"+name+"' that follows the naming convention used to specify a one to many relationship with another entity.  When attempting to create an instance of this class, the following problem was encountered: "+e);
				}
			}
		}
	}
}
